package de.upb.aquarium.api.camera;

import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;

import de.upb.aquarium.World;
import de.upb.aquarium.api.utility.VectorDouble;


/**
 * Simple wrapper for a camera. Has a position and an up vector.
 * The direction of view may be determined in two ways:
 * <ol>
 * 	<li>Give a point to view at.</li>
 * 	<li>Give a direction to view into.</li>
 * </ol>
 * Changing one of these usually also changes the other one. The parameter
 * <code>directionFixed</code> controls these changes. If the direction is
 * fixed, the point viewed at is also changed, but the viewing direction
 * stays unchanged. If the direction is not fixed, the point viewed at is
 * not changed, but the direction is changed. 
 * @author Jost
 */
public class Camera {
	
	private IProjection projection;
	
	private VectorDouble position;
	private VectorDouble at;
	private VectorDouble direction;
	private VectorDouble up;
	
	private boolean directionFixed;

	/**
	 * Constructs a camera.
	 * @param projection the projection to use.
	 * @param position the position the camera is at.
	 * @param at the position the camera views at.
	 * @param up the direction which is to be considered top.
	 */
	public Camera(IProjection projection,
				  VectorDouble position,
				  VectorDouble at,
				  VectorDouble up) {
				
		this.projection = projection;
		
		this.position	= new VectorDouble(position);
		this.at			= new VectorDouble(at);;
		this.up			= new VectorDouble(up);;
		this.direction	= at.sub(position);
			
		directionFixed = false;
	}
	
	/**
	 * Prepares the camera for rendering, i.e calls the
	 * <code>addProjectionMatrix()</code> of the projection member and
	 * multiplies the model view matrix by the camera transformation matrix.
	 * <b>Note:</b> This method does <i>not</i> reset any of the two matrixes
	 * to the identity before modifying them.
	 */
	public void prepareRendering()
	{
		GL gl = World.getInstance().getGL();
		GLU glu = World.getInstance().getGLU();
		
		projection.addProjectionMatrix();
		
		gl.glMatrixMode(GL.GL_MODELVIEW);
		glu.gluLookAt(position.data[0], position.data[1], position.data[2],
					  at.data[0], at.data[1], at.data[2],
					  up.data[0], up.data[1], up.data[2]);
	}

	/**
	 * Moves the camera by the given vector.
	 * @param offset the offset to add to the cameras position.
	 */
	public void moveBy(VectorDouble offset)
	{
		setPosition(position.add(offset));
	}
	
	public IProjection getProjection() {
		return projection;
	}

	public void setProjection(IProjection projection) {
		this.projection = projection;
	}

	public VectorDouble getPosition() {
		return position;
	}

	public void setPosition(VectorDouble position) {
		this.position = position;
		
		if (directionFixed)
		{
			at = position.add(direction);
		}
	}

	public VectorDouble getAt() {
		return at;
	}

	public void setAt(VectorDouble at) {
		this.at = at;
		
		if (directionFixed)
		{
			position = at.sub(direction);
		}
	}

	public VectorDouble getDirection() {
		return direction;
	}

	public void setDirection(VectorDouble direction) {
		this.direction = direction;
		
		at = position.add(direction);
	}

	public VectorDouble getUp() {
		return up;
	}

	public void setUp(VectorDouble up) {
		this.up = up;
	}

	public boolean isDirectionFixed() {
		return directionFixed;
	}

	public void setDirectionFixed(boolean directionFixed) {
		this.directionFixed = directionFixed;
	}
}
